from flask import Flask, request, render_template_string, Response, redirect, jsonify
from werkzeug.utils import secure_filename
import os
import logging
import requests
import json
import queue
import threading
import time
import pandas as pd

app = Flask(__name__)
app.secret_key = 'your_secret_key'

UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads")
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

log_queue = queue.Queue()
job_done = False


class QueueHandler(logging.Handler):
    def emit(self, record):
        try:
            msg = self.format(record)
            log_queue.put(msg)
        except Exception:
            self.handleError(record)


logger = logging.getLogger("zentao_import")
logger.setLevel(logging.INFO)
for handler in logger.handlers[:]:
    logger.removeHandler(handler)
queue_handler = QueueHandler()
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
queue_handler.setFormatter(formatter)
logger.addHandler(queue_handler)


# -------------------- 业务函数 --------------------
def get_token(ZENTAO_URL, account, password):
    logger.info("开始获取Token...")
    try:
        response = requests.post(
            f"{ZENTAO_URL}/tokens",
            json={"account": account, "password": password},
            timeout=10
        )
        response.raise_for_status()
        token = response.json().get("token")
        logger.info("Token获取成功。")
        return token
    except Exception as e:
        logger.error(f"获取Token失败: {e}")
        return None


def get_existing_cases(ZENTAO_URL, token, product_id):
    logger.info("开始获取现有用例...")
    headers = {"Token": token}
    existing_cases = {}
    try:
        response = requests.get(
            f"{ZENTAO_URL}/products/{product_id}/testcases?limit=5000",
            headers=headers,
            timeout=10
        )
        response.raise_for_status()
        response_json = response.json()
        if response_json.get("testcases"):
            for case in response_json["testcases"]:
                case_id = case["id"]
                case_title = case["title"]
                existing_cases[case_title] = case_id
        logger.info(f"获取到 {len(existing_cases)} 个用例。")
        return existing_cases
    except Exception as e:
        logger.error(f"获取用例失败: {e}")
        return None


def parse_steps(steps_str, expected_str):
    logger.info(f"开始解析步骤数据... 预期：{expected_str}")
    steps = []
    try:
        steps_json = json.loads(steps_str)
        for step in steps_json:
            steps.append({
                "desc": step.get("desc", ""),
                "expect": expected_str
            })
    except json.JSONDecodeError:
        logger.warning(f"步骤不是有效的JSON格式，按纯文本处理: {steps_str}")
        step_lines = steps_str.split("\n")
        expect_lines = expected_str.split("\n") if expected_str else []
        for i, line in enumerate(step_lines):
            expect = expect_lines[i].strip() if i < len(expect_lines) else ""
            steps.append({
                "desc": line.strip(),
                "expect": expect
            })
    steps = [step for step in steps if step["desc"]]
    logger.info(f"解析后的步骤数据: {steps}")
    return steps


def import_cases(ZENTAO_URL, product_id, csv_path, token):
    logger.info("导入csv...")
    headers = {"Token": token, "Content-Type": "application/json"}
    new_cases = {}

    if not os.path.isfile(csv_path):
        error_msg = f"错误：文件 '{csv_path}' 不存在。"
        logger.error(error_msg)
        return error_msg

    logger.info(f"正在读取CSV文件: {csv_path}")
    try:
        df = pd.read_csv(csv_path, encoding='GBK', sep=None, engine='python')
        header_fields = list(df.columns)
        logger.info(f"实际读取到的表头字段：{header_fields}")
        REQUIRED_CSV_FIELDS = ["用例名称", "优先级", "用例类型", "步骤"]
        missing_fields = [field for field in REQUIRED_CSV_FIELDS if field not in header_fields]
        if missing_fields:
            error_msg = f"错误：CSV缺少必要的表头字段: {', '.join(missing_fields)}"
            logger.error(error_msg)
            return error_msg
        for index, row in df.iterrows():
            cleaned_row = {}
            for k in row.index:
                value = row[k]
                if pd.isna(value):
                    cleaned_value = ""
                else:
                    cleaned_value = str(value).strip().replace('""', '"')
                cleaned_row[k] = cleaned_value
            for field in REQUIRED_CSV_FIELDS:
                if field not in cleaned_row or cleaned_row[field] == "":
                    error_msg = f"错误：行 {index} 中缺少或为空必要字段 '{field}'，行内容: {cleaned_row}"
                    logger.error(error_msg)
                    return error_msg
            title = cleaned_row["用例名称"]
            if title in new_cases:
                error_msg = f"错误：标题重复 '{title}'"
                logger.error(error_msg)
                return error_msg
            logger.info(f"正在解析用例: {title}")
            case_data = {
                "title": cleaned_row["用例名称"],
                "pri": int(cleaned_row["优先级"]),
                "type": cleaned_row["用例类型"],
                "steps": parse_steps(cleaned_row["步骤"], cleaned_row.get("预期", "")),
                "module": cleaned_row.get("所属模块", ""),
                "precondition": cleaned_row.get("前置条件", ""),
                "keywords": cleaned_row.get("关键词", ""),
                "stage": cleaned_row.get("适用阶段", "")
            }
            new_cases[title] = case_data
    except Exception as e:
        error_msg = f"读取CSV文件时出错: {e}"
        logger.error(error_msg)
        return error_msg

    logger.info(f"CSV文件读取完成，共读取到 {len(new_cases)} 个用例。")

    existing_cases = get_existing_cases(ZENTAO_URL, token, product_id)
    if existing_cases is None:
        error_msg = "获取现有用例失败。"
        logger.error(error_msg)
        return error_msg

    new_count = 0
    update_count = 0
    delete_count = 0

    for title, case_data in new_cases.items():
        if title in existing_cases:
            case_id = existing_cases[title]
            actual_case_id = case_id.split("_")[1] if "_" in case_id else case_id
            logger.info(f"正在更新用例: {title} (ID: {actual_case_id})")
            try:
                response = requests.put(
                    f"{ZENTAO_URL}/testcases/{actual_case_id}",
                    headers=headers,
                    json=case_data,
                    timeout=10
                )
                response.raise_for_status()
                update_count += 1
                logger.info(f"更新成功: {title} (ID: {actual_case_id})")
            except Exception as e:
                logger.error(f"更新失败 {title}: {e}")
        else:
            logger.info(f"正在创建新用例: {title}")
            try:
                response = requests.post(
                    f"{ZENTAO_URL}/products/{product_id}/testcases",
                    headers=headers,
                    json=case_data,
                    timeout=10
                )
                response.raise_for_status()
                new_count += 1
                new_id = response.json().get("id")
                logger.info(f"新增成功: {title} (ID: {new_id})")
            except Exception as e:
                logger.error(f"新增失败 {title}: {e}")

    for title, case_id in existing_cases.items():
        if title not in new_cases:
            actual_case_id = case_id.split("_")[1] if "_" in case_id else case_id
            logger.info(f"正在删除用例：{title}")
            try:
                response = requests.delete(
                    f"{ZENTAO_URL}/testcases/{actual_case_id}",
                    headers=headers,
                    timeout=10
                )
                response_json = response.json()
                if response.status_code == 200:
                    if response_json.get("message") == "success":
                        delete_count += 1
                        logger.info(f"删除成功：{title}")
                    else:
                        logger.error(f"删除失败，返回的message: {response_json.get('message')}")
                else:
                    logger.error(f"删除失败 {title}: 状态码 {response.status_code}, 返回内容: {response.text}")
            except Exception as e:
                logger.error(f"删除失败 {title}: {e}")

    summary_msg = (
        f"<strong>操作完成：共新建<span style='color: green;'>{new_count}</span>个用例，"
        f"共更新<span style='color: blue;'>{update_count}</span>个用例，"
        f"共删除<span style='color: red;'>{delete_count}</span>个废弃用例。</strong>"
    )
    logger.info(summary_msg)
    return summary_msg


def background_job(ZENTAO_URL, account, password, product_id, csv_path):
    global job_done
    token = get_token(ZENTAO_URL, account, password)
    if token is None:
        logger.error("获取Token失败，请检查账号或密码。")
        job_done = True
        if os.path.exists(csv_path):
            os.remove(csv_path)
        return
    result = import_cases(ZENTAO_URL, product_id, csv_path, token)
    logger.info(result)
    if os.path.exists(csv_path):
        os.remove(csv_path)
    job_done = True


# -------------------- 路由 --------------------
@app.route("/start", methods=["POST"])
def start_job():
    global job_done
    job_done = False
    account = request.form.get("account")
    password = request.form.get("password")
    product_id = request.form.get("product_id")
    csv_file = request.files.get("csv_file")
    ZENTAO_URL = request.form.get("zentao_url")

    if not ZENTAO_URL or not account or not password or not product_id or not csv_file:
        return "请填写所有字段并上传CSV文件！"

    filename = secure_filename(csv_file.filename)
    tmp_csv_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    csv_file.save(tmp_csv_path)
    logger.info(f"CSV文件已保存到: {tmp_csv_path}")

    thread = threading.Thread(target=background_job, args=(ZENTAO_URL, account, password, product_id, tmp_csv_path))
    thread.start()

    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>导入测试用例-实时日志</title>
    <style>
        body { 
            display: flex; 
            justify-content: center; 
            align-items: center; 
            height: 100vh;
            font-family: Arial, sans-serif;
            background: #f4f4f4;
        }
        .container {
            text-align: center;
            border: 1px solid #ccc;
            padding: 20px;
            border-radius: 10px;
            width: 80%;
            max-width: 800px;
            background: white;
        }
        #log {
            text-align: left;
            background: #f8f8f8;
            padding: 10px;
            overflow: auto;
            height: 400px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>导入测试用例-实时日志</h1>
        <div id="log"></div>
        <a href="/">返回首页</a>
    </div>
    <script>
        var source = new EventSource("/stream");
        source.onmessage = function(event) {
            var logElem = document.getElementById("log");
            var formattedMessage = event.data;
            if (event.data.indexOf("新增成功") !== -1) {
                formattedMessage = '<span style="color: green;">' + event.data + '</span>';
            } else if (event.data.indexOf("更新成功") !== -1) {
                formattedMessage = '<span style="color: blue;">' + event.data + '</span>';
            } else if (event.data.indexOf("删除成功") !== -1) {
                formattedMessage = '<span style="color: red;">' + event.data + '</span>';
            }
            logElem.innerHTML += formattedMessage + "<br>";
            logElem.scrollTop = logElem.scrollHeight;
            if(event.data.indexOf("操作完成") !== -1 || event.data.indexOf("获取Token失败") !== -1){
                source.close();
            }
        };
    </script>
</body>
</html>
''')


@app.route("/stream")
def stream():
    def generate():
        while not job_done or not log_queue.empty():
            try:
                msg = log_queue.get(timeout=1)
                yield f"data: {msg}\n\n"
            except queue.Empty:
                time.sleep(0.5)
                continue

    return Response(generate(), mimetype="text/event-stream")


@app.route("/query-products", methods=["POST"])
def query_products():
    data = request.json
    ZENTAO_URL = data.get("zentao_url")
    account = data.get("account")
    password = data.get("password")

    if not ZENTAO_URL or not account or not password:
        return jsonify({"error": "缺少必要参数"}), 400

    token = get_token(ZENTAO_URL, account, password)
    if not token:
        return jsonify({"error": "认证失败，请检查账号密码"}), 401

    try:
        response = requests.get(
            f"{ZENTAO_URL}/products",
            headers={"Token": token},
            timeout=10
        )
        response.raise_for_status()
        products_data = response.json()
        products = [
            {"name": p["name"], "id": p["id"]}
            for p in products_data.get("products", [])
        ]
        return jsonify({"products": products})
    except requests.exceptions.RequestException as e:
        return jsonify({"error": f"接口请求失败: {str(e)}"}), 500
    except Exception as e:
        return jsonify({"error": f"数据处理失败: {str(e)}"}), 500


@app.route("/product-query", methods=["GET"])
def product_query_page():
    return render_template_string('''
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>产品ID查询</title>
    <style>
        body { 
            font-family: Arial, sans-serif;
            background: #f4f4f4;
            margin: 0;
            padding: 20px;
            height: 100vh;
        }
        .container {
            width: 90%;
            max-width: 600px; /* 限制整体最大宽度 */
            margin: 30px auto;
            padding: 25px;
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            min-height: 80vh;
        }
        h2 {
            text-align: center;
            margin-bottom: 25px;
            color: #343a40;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #495057;
        }
        input {
            display: block;
            width: 100%;
            max-width: 100%;
            padding: 12px 14px;
            border: 2px solid #dee2e6;
            border-radius: 6px;
            box-sizing: border-box;
            font-size: 15px;
        }
        input:focus {
            border-color: #80bdff;
            outline: none;
            box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
        }
        .form-actions {
            text-align: center;
            margin-top: 30px;
        }
        button.query-btn {
            background: #007bff;
            color: white;
            border: none;
            padding: 12px 30px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 16px;
            transition: background 0.3s;
        }
        button.query-btn:hover {
            background: #0056b3;
        }
        /* 产品列表 */
        #product-list {
            margin-top: 25px;
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 20px;
            max-height: 65vh;
            overflow-y: auto;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 8px;
        }
        .product-item {
            background: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.05);
            transition: transform 0.2s;
        }
        .product-item:hover {
            transform: translateY(-3px);
        }
        .back-link {
            display: inline-block;
            margin-top: 25px;
            padding: 8px 16px;
            background: #6c757d;
            color: white;
            border-radius: 4px;
            text-decoration: none;
            transition: background 0.3s;
        }
        .back-link:hover {
            background: #5a6268;
        }
        /* 小屏幕时显示2列或1列 */
        @media (max-width: 768px) {
            #product-list {
                grid-template-columns: repeat(2, 1fr);
            }
        }
        @media (max-width: 480px) {
            #product-list {
                grid-template-columns: 1fr;
            }
            button.query-btn {
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>产品ID查询</h2>
        <div class="form-group">
            <label>禅道地址</label>
            <!-- 先给定一个默认值 -->
            <input type="text" id="zentao_url" required 
                   placeholder="http://127.0.0.1:80/zentao/api.php/v1">
        </div>
        <div class="form-group">
            <label>禅道账号</label>
            <input type="text" id="account" required>
        </div>
        <div class="form-group">
            <label>密码</label>
            <input type="password" id="password" required>
        </div>
        <div class="form-actions">
            <button class="query-btn" onclick="queryProducts()">立即查询</button>
        </div>
        <div id="product-list"></div>
        <div style="text-align:center; margin-top:25px;">
            <a href="/" class="back-link">返回首页</a>
        </div>
    </div>
    <script>
        // 页面加载时，尝试从 localStorage 里读取已有的值
        document.addEventListener('DOMContentLoaded', () => {
            const storedUrl = localStorage.getItem('zentao_url');
            const storedAccount = localStorage.getItem('zentao_account');
            const storedPassword = localStorage.getItem('zentao_password');

            // 如果没有存储，则默认用 127.0.0.1 这个地址
            if (storedUrl) {
                document.getElementById('zentao_url').value = storedUrl;
            } else {
                document.getElementById('zentao_url').value = 'http://127.0.0.1:80/zentao/api.php/v1';
            }
            if (storedAccount) {
                document.getElementById('account').value = storedAccount;
            }
            if (storedPassword) {
                document.getElementById('password').value = storedPassword;
            }
        });

        function queryProducts() {
            const zentaoUrl = document.getElementById('zentao_url').value;
            const account = document.getElementById('account').value;
            const password = document.getElementById('password').value;

            if (!zentaoUrl || !account || !password) {
                alert('请填写所有必填字段');
                return;
            }

            // 将本次输入保存到 localStorage，方便下次自动填充
            localStorage.setItem('zentao_url', zentaoUrl);
            localStorage.setItem('zentao_account', account);
            localStorage.setItem('zentao_password', password);

            const productListDiv = document.getElementById('product-list');
            productListDiv.innerHTML = '<div class="product-item">查询中...</div>';

            fetch('/query-products', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ zentao_url: zentaoUrl, account, password })
            })
            .then(response => response.json())
            .then(data => {
                let html = '';
                if (data.products?.length > 0) {
                    data.products.forEach(product => {
                        html += `
                        <div class="product-item">
                            <div style="display: flex; justify-content: space-between; align-items: center">
                                <div style="flex:1">
                                    <div style="font-size:14px; color:#333; margin-bottom:4px;">${product.name}</div>
                                    <div style="font-size:12px; color:#666;">ID: ${product.id}</div>
                                </div>
                                <button style="padding:6px 12px; background:#e9ecef; border-radius:4px; border:none; cursor:pointer" 
                                        onclick="copyToClipboard('${product.id}')">
                                    复制
                                </button>
                            </div>
                        </div>`;
                    });
                } else {
                    html = '<div class="product-item" style="color:#666;">未查询到产品信息</div>';
                }
                productListDiv.innerHTML = html;
            })
            .catch(error => {
                productListDiv.innerHTML = `<div class="product-item" style="color:red;">请求失败：${error.message}</div>`;
            });
        }

        function copyToClipboard(text) {
            navigator.clipboard.writeText(text).then(() => {
                alert("已成功复制ID: " + text);
            }).catch(err => {
                console.error('复制失败:', err);
                alert("复制失败，请手动选择复制");
            });
        }
    </script>
</body>
</html>
''')


@app.route("/import", methods=["GET"])
def index():
    return render_template_string("""
<!DOCTYPE html>
<html>
<head>  
    <meta charset="UTF-8">
    <title>禅道用例导入工具</title>
    <style>
        .top-link {
            position: absolute;
            top: 5px;
            right: 70px;
            background: #28a745;
            color: white;
            padding: 8px 15px;
            border-radius: 4px;
            text-decoration: none;
            transition: background 0.3s;
            font-size: 14px;
        }
        .top-link:hover {
            background: #218838;
            color: white;
        }
        body {
            background: #f4f4f4;
            font-family: Arial, sans-serif;
            position: relative; 
        }
        .container {
            width: 400px;
            margin: 50px auto;
            padding: 20px;
            background: #fff;
            border: 1px solid #ccc;
            border-radius: 10px;
            position: relative; 
        }
        .form-group {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
        }
        .form-group label {
            width: 100px;
            text-align: right;
            margin-right: 10px;
        }
        .form-group input {
            flex: 1;
            padding: 8px;
            font-size: 14px;
        }
        .input-group {
            display: flex;
            gap: 5px;
        }
        .file-group {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
            position: relative;
        }
        .file-group input[type="text"] {
            flex: 1;
            padding: 8px;
            font-size: 14px;
            cursor: pointer;
        }
        .file-group input[type="file"] {
            opacity: 0;
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0,0,0,0);
            border: 0;
        }
        .file-group button {
            margin-left: 10px;
            padding: 8px 12px;
            cursor: pointer;
        }
        button.submit-btn {
            display: block;
            margin: 0 auto;
            padding: 8px 20px;
            font-size: 16px;
            cursor: pointer;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
        }
        button.submit-btn:hover {
            background: #0056b3;
        }
        h1 {
            text-align: center;
            margin-bottom: 20px;
        }
        .hint {
            font-size: 12px;
            color: #666;
            text-align: left;
            margin: -10px 0 15px 100px;
            line-height: 1.5;
        }
        #product-id-group {
            align-items: stretch;
        }
        #product-id-group button {
            padding: 8px 12px;
            white-space: nowrap;
            background: #28a745;
        }
        #product-id-group button:hover {
            background: #218838;
        }
    </style>
</head>
<body>
    <a href="http://127.0.0.1:5001/" class="top-link">访问xmind2testcase工具</a>
    <div class="container">
       <h1>禅道用例导入工具</h1>
        <form id="import-form" action="/start" method="post" enctype="multipart/form-data">
            <div class="form-group">
                <label for="zentao_url">禅道地址:</label>
                <input type="text" id="zentao_url" name="zentao_url" 
                       value="http://127.0.0.1:80/zentao/api.php/v1" required>
            </div>
            <div class="hint">例：http://127.0.0.1:80/zentao/api.php/v1<br>（请确保地址以http://或https://开头）</div>

            <div class="form-group">
                <label for="account">禅道账号:</label>
                <input type="text" id="account" name="account" required>
            </div>
            <div class="form-group">
                <label for="password">密码:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="form-group" id="product-id-group">
                <label for="product_id">产品ID:</label>
                <div class="input-group" style="flex:1;">
                    <input type="text" id="product_id" name="product_id" required>
                    <button type="button" onclick="window.location.href='/product-query'">产品ID查询</button>
                </div>
            </div>
            <div class="file-group">
                <label for="csv_file">CSV文件:</label>
                <input type="text" id="csv_file_text" placeholder="请选择CSV文件..." readonly>
                <input type="file" id="csv_file" name="csv_file" accept=".csv" required>
                <button type="button" id="choose-file">选择文件</button>
            </div>
            <button type="submit" class="submit-btn">开始导入</button>
        </form>
    </div>
    <script>
        document.getElementById("csv_file_text").addEventListener("click", function() {
            document.getElementById("csv_file").click();
        });
        document.getElementById("choose-file").addEventListener("click", function() {
            document.getElementById("csv_file").click();
        });
        document.getElementById("csv_file").addEventListener("change", function(e) {
            document.getElementById("csv_file_text").value = e.target.files[0].name;
        });

        document.getElementById("import-form").addEventListener("submit", function(){
            localStorage.setItem("zentao_url", document.getElementById("zentao_url").value);
            localStorage.setItem("account", document.getElementById("account").value);
            localStorage.setItem("password", document.getElementById("password").value);
            localStorage.setItem("product_id", document.getElementById("product_id").value);
        });

        window.onload = function(){
            const fields = ["zentao_url", "account", "password", "product_id"];
            fields.forEach(field => {
                if(localStorage.getItem(field)){
                    document.getElementById(field).value = localStorage.getItem(field);
                }
            });
        };
    </script>
</body>
</html>
""")


@app.route("/", methods=["GET"])
def home():
    return redirect("/import")


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=False)